https://github.com/howarder3/ironman2021_PyQt5_photoshop/tree/main/day28_final_project_day1
我們接下來的討論,會基於讀者已經先讀過我 day5 文章 的架構下去進行程式設計
如果還不清楚我程式設計的邏輯 (UI.py、controller.py、start.py 分別在幹麻)
建議先閱讀 day5 文章後再來閱讀此文。
https://www.wongwonggoods.com/python/pyqt5-5/
雖然原本我有想直接拿 Day17 的內容繼續改,
後來覺得架構上還不夠漂亮,最後決定乾脆直接砍掉重練比較快哈哈哈。
這次我設計的 final project UI 初版長這樣
欸等等先別吐血啊!!! 這 UI 不是一天搞出來的啊!!
UI 就請大家自己慢慢刻哈哈哈哈哈!!!
(你說突然這也突然跳太高階了吧?!
其實只是設定一些顏色而已XD,之前因為這個相對簡單就沒特別介紹)
如果還有機會的話有空在介紹,不過這邊的內容多半是用於排版,
所以也難能以單一例子舉出 demo 在幹嘛,
總之可以自己玩玩看 Layouts 這一塊,
先「拉一個 Layout」,再把「要排版的元件丟進去」,就對自動對整齊了!
使用的應該是類似 css 語法? 我不確定XD
總之有興趣可以直接參考下面的延伸閱讀進行修改,
設定位置在 Qt desinger 的這邊
延伸閱讀:給想研究更多怎麼設定顏色的人,請參考 Qt Style Sheets Examples
Qlabel 就是一個我們可以輸入圖片的地方,
透過右邊的「...」,選擇一張我們要的圖片,就可以直接把 LOGO 嵌入 UI 中了。
一樣,這程式只有介面 (視覺上的呈現),沒有任何互動功能
python UI.py
讚到不行XDDDD (自己講
這次的 final project 真的內容會做太多,如果沒有好好規劃一下架構,
很有可能寫出來的東西會一團亂XDDD
所以我們要先趁今天來規劃,明天再來慢慢把功能實現
依照 day5 的設計,我們至少也會有
而 UI 因為我們在 Qtdesigner 已經設計得夠複雜了,也不太需要另外寫程式,
所以基本上在經由 pyuic 的轉換後,就已經完成了 UI.py
pyuic5 -x day28.ui -o UI.py
controller 是我們這邊要討論的重點,在我們的 photoshop 後端邏輯裡面,
我之前在 day17 實作的最終版是把「圖像+圖像變化方法」寫在同一個 class 當中,
我們如果繼續讓我們的「變化方法增加」,不適不能這樣實作,
但最終我們會有一個超級巨大的 class 共同實作 「圖像+圖像變化方法」,
於是我決定把「變化方法」拆出去獨立一個 class,
使得「圖像」與「變化方法」分開成兩套獨立機制,而能互向協助。
這也符合 design pattern 的 Interface Segregation Principle(ISP) 介面隔離原則
以遊戲來說,我們也可以舉例:
我們大可以把一個角色的所有「屬性、裝備、技能」全部都包在這個角色當中,
但如果另外一個玩家也玩了同一個職業,我們何必把技能「整個複製進去另外一個角色當中呢?
」
於是我們可以把「技能」這個介面分離出去,讓有需要的類別再去呼叫這個介面即可。
因此,這時候就能透過這樣的拆分方式,把我們實作的彈性與擴充功能的彈性提升。
套用 design pattern 前,基本上這個設計沒什麼問題,
硬要說缺點只是維護職業技能時,「需要一個個角色去調整內部的技能
」
套用 design pattern 的 Interface Segregation Principle(ISP) 介面隔離原則後,
我們把技能獨立出來維護,這樣的好處就是我們可以調整一次職業技能,
所有的角色都能得到修正!
而這邊我們也要做同樣的事情,我們要把
「修改圖片的方法」這個介面獨立出來!
獨立「圖片本身」與「圖片處理方法」,
另外因為滑鼠事件的資訊比較特別,需要從圖片上獲取,所以我們也另外獨立出來處理
第一次設計這個系統的時候,就碰到一個最關鍵的問題,
「沒有提早先想好架構就開始寫,導致繞了很多彎路
」,
應該要先想好再下去寫的問題就是,在這個系統裡面,「什麼事情會是觸發事件的 trigger?
」
如果我們不知道「什麼事件會被使用者觸發
」,
「就不知道要從哪個點開始啟動後續的修改
」,
我一開始就是瘋狂地開始寫功能XDD,然後沒注意誰呼叫誰,
結果功能都有了,但是 trigger 位置寫在錯誤的 class,
所以要重搞順序XDD
所以在寫系統時,我們需要特別注意,
我們箭頭指的地方就是「會觸發事件的 trigger
」,
凡經過此處的程式都是「一次修改的起點
」,引導我們進行往後的修改。
基本上我們設計的「修改圖片」都有符合一些同樣的原則,
我們可以使用「介面繼承」的方式來實現,
為了達到這樣的效果,我們需要 「import abc
」 這個 python 酷酷的 library,
細節的部分我會再另外寫一篇文章,這邊我們先直接實作。
註:冷知識(?) abc = the infrastructure for defining 「abstract base classes (ABCs)」 in Python
簡單來說,就是「抽象的類別 (class)」定義
我們預計設計的架構如下,一共會有三層,底下會一層層介紹:
我們可以大致歸納我們全部的變化介面都會有兩個基本的要素:
__init__
另外,我們強制這兩個介面在繼承後都必須被定義,
所以我們透過「@abc.abstractmethod」,定義這個抽象的方法 (未實作功能),
以及「return NotImplemented」,如果使用者繼承介面後未定義這個函數,
會跳 「NotImplemented error」,強制跳錯 (逼使用者一定要定義介面內容)。
import abc
class method_interface(abc.ABC):
@abc.abstractmethod
def __init__(self):
return NotImplemented
@abc.abstractmethod
def update_img(self):
return NotImplemented
我們會實作的「圖像變化方法」,能夠產生變化的方法大概有兩種,
一種是滑條類,另一種是畫筆類。
這兩類在實作時,都會繼承我們的介面大祖宗 (method_interface),
然後再分別基於滑條的特性與畫筆的特性,實作各自的介面。
這所有的介面方法都屬於「圖片變化方法」,所以我們會強烈推薦用繼承的方法撰寫。
會比起一個個慢慢定義有更好的架構與維護性
(維護一個父類別,也許底下的所有子類別都能全部受惠到這次的更動。不用一個個慢慢改。)
最大的差別就是:
因此這兩個方法我們要分開實作,實作細節一樣我們明日再提,
今天光是講大架構就已經夠累了。
在看該方法是「滑條方法介面 slider_method_interface」或是「畫筆方法介面 pen_method_interface」後,
我們就可以開始實作細部功能了,這部分的實作細節我們就明日在提吧。
(今天光是講完大架構相信大家已經都累了XDDD)
這個是舊版,滑鼠那部分因為是新加的,目前還沒更新上去,
不過相信上面已經說明得相當完整了,應該能大約類推出他在圖形上會呈現的細節
★ 本文也同步發於我的個人網站(會有內容目錄與顯示各個小節,閱讀起來更流暢):【PyQt5】Day 28 final project - 1 / 來搞一個自己的 photoshop 吧!UI 篇 + 純程式架構篇 (結合 PyQt + OpenCV)